/*
 * FileOperations.cpp
 *
 *  Created on: Apr 3, 2010
 *      Author: Lukasz Spintzyk
 */

#include "FileOperations.h"
#include "DirOperations.h"
#include "OtherFsOperations.h"
#include "ph.h"
#include "CommandLineParsers.h"
#include "Filesystem.h"
#include "DirectoryReader.h"
#include "InodeReader.h"
#include "File.h"
#include "FileContainer.h"
#include "utils.h"
#include "FSException.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>


namespace CudaCryptoFs {

int open(const char *path, struct fuse_file_info *fi) {
	// TODO write and plug
	try {
		Filesystem* fs = getFS();
		FileContainer *fc = fs->getFileContainer();

		if (fc->hasFile(path)) {
			fi->fh = fc->getFileHandle(path);
		} else {
			DirectoryReader* dMgr = fs->getDirectoryReader();
			boost::shared_ptr<Inode> file_inode = dMgr->path2Inode(path);
			assert(file_inode->isFile());
			uint64_t fh = fc->addFile(path, file_inode);
			fi->fh = fh;
		}

		//TODO check permissions, what permisions ?
	} catch (FSException& e) {
		return -e.code;
	}

	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int read(const char *path, char *buf, size_t size, off_t offset,
		struct fuse_file_info *fi)
{
	try {
		FilePtr file = get_file(path, fi->fh);
		return file->read(buf, size, offset);

	} catch (FSException& e) {
		return -e.code;
	}
}

/*****************************************************************************/
/*****************************************************************************/

int write(const char *path, const char *buff, size_t size, off_t offset,
		struct fuse_file_info *fileinfo) {
	// TODO write an plug
	try {
		FilePtr file = get_file(path, fileinfo->fh);
		uint32_t bytes_written =
				file->write(buff, size, offset);
		return bytes_written;
	} catch (FSException& e) {
		return -e.code;
	}

}

/*****************************************************************************/
/*****************************************************************************/

FilePtr get_file(const char* path, uint64_t fh) {
	Filesystem* fs = getFS();
	FileContainer *fc = fs->getFileContainer();
	if (fc->hasFile(path)) {
		return fc->getFile(path);
	} else {
		DirectoryReader* dMgr = fs->getDirectoryReader();
		boost::shared_ptr<Inode> file_inode = dMgr->path2Inode(path);
		fc->addFile(path, file_inode);
		return fc->getFile(path);
	}
}

/*****************************************************************************/
/*****************************************************************************/

int unlink(const char *filepath) {
	// TODO write an plug
	try
	{
		Filesystem* fs = getFS();
		InodeManager* ir = fs->getInodeReader();
		DirectoryReader* dr = fs->getDirectoryReader();
		fs->getFileContainer()->removeFileIfExist(filepath);

		std::string file_to_del = findLastName(filepath);

		InodePtr inode = dr->path2Inode(getParentDir(filepath));
		assert(inode->isDir());

		InodePtr fileInode = dr->path2Inode(filepath);
		boost::shared_ptr<DirectoryIterator> it = dr->begin(inode);
		remove_dir_entry_from_directory(it, file_to_del);

		ir->decrementLinksCount( fileInode );

	}
	catch (FSException& e)
	{
		return -e.code;
	}
	return 0;
}

int truncate(const char *, off_t) {
	// TODO write an plug
	return -ENOENT;
}

/*****************************************************************************/
/*****************************************************************************/

int flush(const char *, struct fuse_file_info *) {
	// TODO write an plug
	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int release(const char *path, struct fuse_file_info *fi) {
	try {
		Filesystem* fs = getFS();
		FileContainer *fc = fs->getFileContainer();

		if (fi->fh)
		{
			if (fc->hasFile(fi->fh))
			{
				fc->release(fi->fh);
				return 0;
			}
		}

		return -ENOENT;

		//TODO check permissions, what permisions ?
	} catch (FSException& e) {
		return -e.code;
	}
}

/*****************************************************************************/
/*****************************************************************************/

int fsync(const char *, int, struct fuse_file_info *) {
	// TODO write an plug
	return -ENOENT;
}

/*****************************************************************************/
/*****************************************************************************/

int create(const char *file_path, mode_t mode, struct fuse_file_info *finfo) {
	try {
		Filesystem* fs = getFS();
		DirectoryReader* dMgr = fs->getDirectoryReader();

		InodeManager* iMgr = fs->getInodeReader();

		std::string parent_dir = getParentDir(file_path);
		std::string file_name = findLastName(file_path);

		boost::shared_ptr<Inode> dir_inode = dMgr->path2Inode(parent_dir);
		assert(dir_inode->isDir());

		InodePtr newInode = iMgr->createNewInode();
		iMgr->reserveBlocksForInode(newInode, 1);
		newInode->d_mode = mode;
		newInode->d_uid = fuse_get_context()->uid;
		newInode->d_gid = fuse_get_context()->gid;
		newInode->d_ctime = time(NULL);
		newInode->d_mtime = time(NULL);
		newInode->d_atime = time(NULL);
		newInode->d_linksCount = 1;

		iMgr->setInode(newInode->d_num, newInode);

		DirEntry file_entry(file_name, newInode->d_num, S_IFREG);
		dMgr->addDirEntry(file_entry, dir_inode);

	} catch (FSException& e) {
		return -e.code;
	}

	return 0;
}

int ftruncate(const char *, off_t, struct fuse_file_info *) {
	// TODO write an plug
	return -ENOENT;
}

/*****************************************************************************/
/*****************************************************************************/

int fgetattr(const char *file_path, struct stat *stbuf,
		struct fuse_file_info *file_info) {
	// TODO write an plug
	try {
		memset(stbuf, 0, sizeof(struct stat));

		Filesystem* fs = getFS();
		DirectoryReader* dr = fs->getDirectoryReader();

		InodePtr file_inode = dr->path2Inode(file_path);

		stbuf->st_mode = file_inode->d_mode;
		stbuf->st_nlink = file_inode->d_linksCount;
		stbuf->st_ctim.tv_sec = file_inode->d_ctime;
		stbuf->st_atim.tv_sec = file_inode->d_atime;
		stbuf->st_mtim.tv_sec = file_inode->d_mtime;
		stbuf->st_size = file_inode->d_size;
		stbuf->st_blocks = file_inode->d_blocks;
		stbuf->st_gid = file_inode->d_gid;
		stbuf->st_uid = file_inode->d_uid;
		stbuf->st_ino = file_inode->d_num;
		//stbuf->st_blksize = fs->getSuperBlock().d_blockSize; //ignored

		return 0;
	} catch (FSException& e) {
		return -e.code;
	}
}

} // end namespace CudaCryptoFs

